package com.gitee.config;

import com.gitee.security.filter.JWTAuthenticationFilter;
import com.gitee.security.filter.JWTLoginFilter;
import com.gitee.security.handler.LoginAuthenticationFailureHandler;
import com.gitee.security.handler.LoginAuthenticationSuccessHandler;
import com.gitee.security.handler.RestAccessDeniedHandler;
import com.gitee.security.handler.RestAuthenticationEntryPoint;
import com.gitee.security.provider.MyAuthenticationProvider;
import com.gitee.service.impl.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * @description: SpringSecurity的配置
 * @author: chennl
 * @date: 2021/12/28
 * @version: 1.0
 */
@Configuration
@ConditionalOnClass(value = {JWTConfig.class})
@EnableWebSecurity
// 使用表达式时间方法级别的安全性 4个注解可用
// -@PreAuthorize 在方法调用之前,基于表达式的计算结果来限制对方法的访问
// -@PostAuthorize 允许方法调用,但是如果表达式计算结果为false,将抛出一个安全性异常
// -@PostFilter 允许方法调用,但必须按照表达式来过滤方法的结果
// -@PreFilter 允许方法调用,但必须在进入方法之前过滤输入值
@EnableGlobalMethodSecurity(prePostEnabled = true)
// 开启@Secured 注解过滤权限
//@EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
    @Autowired
    private LoginAuthenticationSuccessHandler authenticationSuccessHandler;
    @Autowired
    private LoginAuthenticationFailureHandler authenticationFailureHandler;
    @Autowired
    private RestAccessDeniedHandler accessDeniedHandler;
    @Autowired
    private UserDetailsServiceImpl userDetailsService;
    @Autowired
    private JWTConfig jwtConfig;
    // 白名单
    final String[] WHITELIST = {
            "/reg",
            "/login",
            // swagger ui
            "/v2/api-docs",
            "/swagger-resources",
            "/swagger-resources/**",
            "/configuration/ui",
            "/configuration/security",
            "/swagger-ui.html",
            "/webjars/**"
    };

    /**
     * 静态资源处理
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(WHITELIST);
    }

    /**
     * 用户访问的资源权限认证
     *      anyRequest          |   匹配所有请求路径
     *      access              |   SpringEl表达式结果为true时可以访问
     *      anonymous           |   匿名可以访问
     *      denyAll             |   用户不能访问
     *      fullyAuthenticated  |   用户完全认证可以访问（非remember-me下自动登录）
     *      hasAnyAuthority     |   如果有参数，参数表示权限，则其中任何一个权限可以访问
     *      hasAnyRole          |   如果有参数，参数表示角色，则其中任何一个角色可以访问
     *      hasAuthority        |   如果有参数，参数表示权限，则其权限可以访问
     *      hasIpAddress        |   如果有参数，参数表示IP地址，如果用户IP和参数匹配，则可以访问
     *      hasRole             |   如果有参数，参数表示角色，则其角色可以访问
     *      permitAll           |   用户可以任意访问
     *      rememberMe          |   允许通过remember-me登录的用户访问
     *      authenticated       |   用户登录后可访问
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        /*// 添加验证码过滤器
        http.addFilterBefore(verifyCodeFilter, UsernamePasswordAuthenticationFilter.class);*/
        // 关闭csrf
        http.csrf().disable()
                // 未登录或者token失效的处理
                .httpBasic().authenticationEntryPoint(restAuthenticationEntryPoint)
                .and()
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                // 添加自定义JWT登录过滤器
                .addFilter(new JWTLoginFilter(authenticationManager(), jwtConfig, userDetailsService))
                // 添加自定义JWT认证过滤器
                .addFilter(new JWTAuthenticationFilter(authenticationManager(), jwtConfig, userDetailsService))
                .formLogin()
                // 登录成功自定义处理
                .successHandler(authenticationSuccessHandler)
                // 登录失败自定义处理
                .failureHandler(authenticationFailureHandler)
                .permitAll()
                .and()
                // 防止iframe 造成跨域
                .headers()
                .frameOptions()
                .disable()
                .and();

        // 禁用缓存
        http.headers().cacheControl();

        // 无权限访问的处理
        http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
    }

    /**
     * 用户登录账号身份认证
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        /*// 方法一
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());*/
        // 方法二：使用自定义身份验证组件
        auth.authenticationProvider(new MyAuthenticationProvider(userDetailsService, passwordEncoder()));
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        // 可以使用自定义的密码编码器，这里使用BCryptPasswordEncoder
        return new BCryptPasswordEncoder(12);
    }
}
